﻿@page "/users"
@using AliasVault.RazorComponents.Tables
@using AliasVault.Shared.Server.Services
@inject ServerSettingsService SettingsService
@inherits MainBase

<LayoutPageTitle>Users</LayoutPageTitle>

<PageHeader
    BreadcrumbItems="@BreadcrumbItems"
    Title="@(TotalRecords > 0 ? $"Users ({TotalRecords:N0})" : "Users")"
    Description="This page shows an overview of all registered users and the associated vaults.">
    <CustomActions>
        <a href="mobile-login-history" class="inline-flex items-center px-3 py-2 text-sm font-medium text-gray-600 bg-gray-100 border border-gray-300 rounded-md hover:bg-gray-200 hover:text-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500 dark:bg-gray-700 dark:text-gray-300 dark:border-gray-600 dark:hover:bg-gray-600 dark:hover:text-gray-200 mr-3">
            <svg class="w-4 h-4 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 18h.01M8 21h8a2 2 0 002-2V5a2 2 0 00-2-2H8a2 2 0 00-2 2v14a2 2 0 002 2z"/>
            </svg>
            Mobile Login History
        </a>
        <RefreshButton OnClick="() => RefreshData(CancellationToken.None)" ButtonText="Refresh" />
    </CustomActions>
</PageHeader>

@if (IsInitialized)
{
    <div class="px-4">
        <ResponsivePaginator CurrentPage="CurrentPage" PageSize="PageSize" TotalRecords="TotalRecords" OnPageChanged="HandlePageChanged" />
        <div class="mb-3 flex space-x-4">
            <div class="w-3/4">
                <div class="relative">
                    <SearchIcon />
                    <input type="text" @bind-value="SearchTerm" @bind-value:event="oninput" id="search" placeholder="Search users..." class="w-full px-4 ps-10 py-2 border rounded text-sm text-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white">
                </div>
            </div>
            <div class="w-1/4">
                <select @bind="SelectedUserFilter" class="w-full px-4 py-2 border rounded text-sm text-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white">
                    <option value="">All Users</option>
                    <option value="active">Active Users</option>
                    <option value="inactive">Inactive Users</option>
                    <option value="blocked">Blocked Users</option>
                    <option value="2fa">2FA Enabled</option>
                </select>
            </div>
        </div>
    </div>
}

@if (IsLoading)
{
    <LoadingIndicator />
}
else
{
    <div class="px-4">
        <SortableTable Columns="@_tableColumns" SortColumn="@SortColumn" SortDirection="@SortDirection" OnSortChanged="HandleSortChanged">
            @foreach (var user in UserList)
            {
                <SortableTableRow>
                    <SortableTableColumn IsPrimary="true">@user.CreatedAt.ToString("yyyy-MM-dd HH:mm")</SortableTableColumn>
                    <SortableTableColumn>@user.UserName</SortableTableColumn>
                    <SortableTableColumn>@user.VaultCount</SortableTableColumn>
                    <SortableTableColumn>@user.CredentialCount</SortableTableColumn>
                    <SortableTableColumn>@user.EmailClaimCount</SortableTableColumn>
                    <SortableTableColumn>@Math.Round((double)user.VaultStorageInKb / 1024, 1) MB</SortableTableColumn>
                    <SortableTableColumn>@(user.LastActivityDate?.ToString("yyyy-MM-dd HH:mm") ?? "Never")</SortableTableColumn>
                    <SortableTableColumn>
                        <div class="flex flex-wrap gap-1">
                            @if (user.IsInactive)
                            {
                                <StatusPill Enabled="false" TextFalse="Inactive" />
                            }
                            @if (user.Blocked)
                            {
                                <StatusPill Enabled="false" TextFalse="Blocked" />
                            }
                            @if (user.TwoFactorEnabled)
                            {
                                <StatusPill Enabled="true" TextTrue="2FA enabled" />
                            }
                        </div>
                    </SortableTableColumn>
                    <SortableTableColumn>
                        <LinkButton Color="primary" Href="@($"users/{user.Id}")" Text="View" />
                    </SortableTableColumn>
                </SortableTableRow>
            }
        </SortableTable>
    </div>
}

@code {
    private readonly List<TableColumn> _tableColumns = [
        new TableColumn { Title = "Registered", PropertyName = "CreatedAt" },
        new TableColumn { Title = "Username", PropertyName = "UserName" },
        new TableColumn { Title = "# Vaults", PropertyName = "VaultCount" },
        new TableColumn { Title = "# Credentials", PropertyName = "CredentialCount" },
        new TableColumn { Title = "# Email claims", PropertyName = "EmailClaimCount" },
        new TableColumn { Title = "Storage", PropertyName = "VaultStorageInKb" },
        new TableColumn { Title = "Last Activity", PropertyName = "LastActivityDate" },
        new TableColumn { Title = "Status", Sortable = false },
        new TableColumn { Title = "Actions", Sortable = false},
    ];

    private List<UserViewModel> UserList { get; set; } = [];
    private bool IsInitialized { get; set; } = false;
    private bool IsLoading { get; set; } = true;
    private int CurrentPage { get; set; } = 1;
    private int PageSize { get; set; } = 50;
    private int TotalRecords { get; set; }

    private string _searchTerm = string.Empty;
    private CancellationTokenSource? _searchCancellationTokenSource;

    /// <summary>
    /// The last search term.
    /// </summary>
    private string _lastSearchTerm = string.Empty;

    private string SearchTerm
    {
        get => _searchTerm;
        set
        {
            if (_searchTerm != value)
            {
                _searchTerm = value;
                _searchCancellationTokenSource?.Cancel();
                _searchCancellationTokenSource = new CancellationTokenSource();
                _ = RefreshData(_searchCancellationTokenSource.Token);
            }
        }
    }

    private string _selectedUserFilter = string.Empty;
    private string _lastSelectedUserFilter = string.Empty;
    private string SelectedUserFilter
    {
        get => _selectedUserFilter;
        set
        {
            if (_selectedUserFilter != value)
            {
                _selectedUserFilter = value;
                _searchCancellationTokenSource?.Cancel();
                _searchCancellationTokenSource = new CancellationTokenSource();
                _ = RefreshData(_searchCancellationTokenSource.Token);
            }
        }
    }

    private string SortColumn { get; set; } = "CreatedAt";
    private SortDirection SortDirection { get; set; } = SortDirection.Descending;

    private async Task HandleSortChanged((string column, SortDirection direction) sort)
    {
        SortColumn = sort.column;
        SortDirection = sort.direction;
        await RefreshData(CancellationToken.None);
    }

    /// <inheritdoc />
    protected override async Task OnInitializedAsync()
    {
        await base.OnInitializedAsync();
        BreadcrumbItems.Add(new BreadcrumbItem { DisplayName = "Users" });
    }

    /// <inheritdoc />
    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            await RefreshData(CancellationToken.None);
        }
    }

    private void HandlePageChanged(int newPage)
    {
        CurrentPage = newPage;
        _ = RefreshData(CancellationToken.None);
    }

    private async Task RefreshData(CancellationToken cancellationToken = default)
    {
        try
        {
            IsLoading = true;
            StateHasChanged();

            var settings = await SettingsService.GetAllSettingsAsync();
            var inactivityCutoffDate = settings.MarkUserInactiveAfterDays > 0
                ? DateTime.UtcNow.AddDays(-settings.MarkUserInactiveAfterDays)
                : (DateTime?)null;

            await using var dbContext = await DbContextFactory.CreateDbContextAsync(cancellationToken);
            IQueryable<AliasVaultUser> query = dbContext.AliasVaultUsers;

            query = ApplySearchFilter(query);
            query = ApplyUserFilter(query, inactivityCutoffDate);
            query = ApplySort(query, dbContext);

            TotalRecords = await query.CountAsync(cancellationToken);
            var users = await query
                .Skip((CurrentPage - 1) * PageSize)
                .Take(PageSize)
                .Select(u => new
                {
                    u.Id,
                    u.UserName,
                    u.CreatedAt,
                    u.TwoFactorEnabled,
                    u.Blocked,
                    u.LastActivityDate,
                    Vaults = u.Vaults.Select(v => new
                    {
                        v.FileSize,
                        v.CreatedAt,
                        v.RevisionNumber,
                        CredentialCount = v.CredentialsCount,
                    }),
                    EmailClaimCount = u.EmailClaims.Count(),
                })
                .ToListAsync(cancellationToken);

            if (cancellationToken.IsCancellationRequested)
            {
                return;
            }

            UserList = users.Select(user =>
            {
                var lastActivity = user.LastActivityDate ?? user.CreatedAt;
                var isInactive = inactivityCutoffDate.HasValue && lastActivity < inactivityCutoffDate.Value;

                return new UserViewModel
                {
                    Id = user.Id,
                    UserName = user.UserName?.ToLower() ?? "N/A",
                    TwoFactorEnabled = user.TwoFactorEnabled,
                    Blocked = user.Blocked,
                    CreatedAt = user.CreatedAt,
                    LastActivityDate = user.LastActivityDate,
                    IsInactive = isInactive,
                    VaultCount = user.Vaults.Count(),
                    CredentialCount = user.Vaults.OrderByDescending(x => x.RevisionNumber).First().CredentialCount,
                    EmailClaimCount = user.EmailClaimCount,
                    VaultStorageInKb = user.Vaults.Sum(x => x.FileSize),
                };
            }).ToList();

            IsLoading = false;
            IsInitialized = true;
            StateHasChanged();
        }
        catch (OperationCanceledException)
        {
            // Expected when cancellation is requested, do nothing
        }
    }

    /// <summary>
    /// Apply search filter to the query.
    /// </summary>
    private IQueryable<AliasVaultUser> ApplySearchFilter(IQueryable<AliasVaultUser> query)
    {
        if (SearchTerm.Length > 0)
        {
            // Reset page number back to 1 if the search term has changed.
            if (SearchTerm != _lastSearchTerm && CurrentPage != 1)
            {
                CurrentPage = 1;
            }
            _lastSearchTerm = SearchTerm;

            var searchTerm = SearchTerm.Trim().ToLower();
            query = query.Where(x => EF.Functions.Like(x.UserName!.ToLower(), "%" + searchTerm + "%"));
        }

        return query;
    }

    /// <summary>
    /// Apply user filter to the query.
    /// </summary>
    private IQueryable<AliasVaultUser> ApplyUserFilter(IQueryable<AliasVaultUser> query, DateTime? inactivityCutoffDate)
    {
        if (!string.IsNullOrEmpty(SelectedUserFilter))
        {
            // Reset page number back to 1 if the filter has changed.
            if (SelectedUserFilter != _lastSelectedUserFilter && CurrentPage != 1)
            {
                CurrentPage = 1;
            }
            _lastSelectedUserFilter = SelectedUserFilter;

            switch (SelectedUserFilter)
            {
                case "active":
                    if (inactivityCutoffDate.HasValue)
                    {
                        query = query.Where(u => u.LastActivityDate >= inactivityCutoffDate.Value || (u.LastActivityDate == null && u.CreatedAt >= inactivityCutoffDate.Value));
                    }
                    query = query.Where(u => !u.Blocked);
                    break;
                case "inactive":
                    if (inactivityCutoffDate.HasValue)
                    {
                        query = query.Where(u => (u.LastActivityDate != null && u.LastActivityDate < inactivityCutoffDate.Value) || (u.LastActivityDate == null && u.CreatedAt < inactivityCutoffDate.Value));
                    }
                    else
                    {
                        // If MarkUserInactiveAfterDays is 0, no users are considered inactive
                        query = query.Where(u => false);
                    }
                    break;
                case "blocked":
                    query = query.Where(u => u.Blocked);
                    break;
                case "2fa":
                    query = query.Where(u => u.TwoFactorEnabled);
                    break;
            }
        }

        return query;
    }

    /// <summary>
    /// Apply sort to the query.
    /// </summary>
    private IQueryable<AliasVaultUser> ApplySort(IQueryable<AliasVaultUser> query, AliasServerDbContext dbContext)
    {
        // Apply sort.
        switch (SortColumn)
        {
            case "Id":
                query = SortDirection == SortDirection.Ascending
                    ? query.OrderBy(x => x.Id)
                    : query.OrderByDescending(x => x.Id);
                break;
            case "CreatedAt":
                query = SortDirection == SortDirection.Ascending
                    ? query.OrderBy(x => x.CreatedAt)
                    : query.OrderByDescending(x => x.CreatedAt);
                break;
            case "UserName":
                query = SortDirection == SortDirection.Ascending
                    ? query.OrderBy(x => x.UserName)
                    : query.OrderByDescending(x => x.UserName);
                break;
            case "VaultCount":
                query = SortDirection == SortDirection.Ascending
                    ? query.OrderBy(x => x.Vaults.Count)
                    : query.OrderByDescending(x => x.Vaults.Count);
                break;
            case "CredentialCount":
                query = SortDirection == SortDirection.Ascending
                    ? query.OrderBy(x => x.Vaults.OrderByDescending(x => x.RevisionNumber).First().CredentialsCount)
                    : query.OrderByDescending(x => x.Vaults.OrderByDescending(x => x.RevisionNumber).First().CredentialsCount);
                break;
            case "EmailClaimCount":
                query = SortDirection == SortDirection.Ascending
                    ? query.OrderBy(x => x.EmailClaims.Count)
                    : query.OrderByDescending(x => x.EmailClaims.Count);
                break;
            case "VaultStorageInKb":
                query = SortDirection == SortDirection.Ascending
                    ? query.OrderBy(x => x.Vaults.Sum(v => v.FileSize))
                    : query.OrderByDescending(x => x.Vaults.Sum(v => v.FileSize));
                break;
            case "LastActivityDate":
                query = SortDirection == SortDirection.Ascending
                    ? query.OrderBy(x => x.LastActivityDate ?? x.CreatedAt)
                    : query.OrderByDescending(x => x.LastActivityDate ?? x.CreatedAt);
                break;
            default:
                query = SortDirection == SortDirection.Ascending
                    ? query.OrderBy(x => x.Id)
                    : query.OrderByDescending(x => x.Id);
                break;
        }

        return query;
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            _searchCancellationTokenSource?.Cancel();
            _searchCancellationTokenSource?.Dispose();
        }
        base.Dispose(disposing);
    }

}
